js 基础-数据类型和变量
2023-07-21 01:27:05 # fontend

1. JS 有哪些数据类型,如何判断这些数据类型 ?

7种基本数据类型: number string boolean symbol null undefined bigint;
1种引用数据类型: object: date array function

  1. BigInt 是 es6 增加的表示任意精度格式的整数,可以安全的存储和操作大整数,即使这个数超过了 number 能够表示的安全范围
  2. 基本数据类型存储在栈中,占据空间小,大小固定,属于频繁被使用的数据;引用数据的栈中存储了指针,该指针指向堆中该实体的起始地址。

判断方法:

  1. typeof 可以判断基本类型,除了null 返回的是 object ,其他都是正确的, typeof 还可以判断 function;
  2. instanceof 可以判断两个对象是否属于实例关系,可以用来判读引用数据类型
  3. Object.prototype.toString.call() 返回字符串 [object type] type 则为数据类型信息,此方法可以判断任何数据类型

基本数据类型和引用数据类型的区别

比较 基本数据类型 引用数据类型
数据存放位置 基本数据类型存放在中,数据大小确定,内存空间大小可以分配 引用数据类型存放在中,每个空间大小不一样,要根据情况进行特定的配置
变量存储内容 变量中存储的是值本身 变量存储的是地址
变量用来赋值时 把变量的复制一份去赋值 把变量的内存地址复制一份去赋值
存储内容大小 存储值较小 存储值较大

堆和栈的区别

比较 栈(线程) 堆(进程,线程共享)
大小固定 创建时,确定大小(值大小固定),故可能会溢出 大小不固定,可随时增加
存储类型 存储基本数据类型及引用类型数据的堆地址 存储引用类型数据
如何访问 按值访问 按引用(堆内存地址)访问
特点 空间小,运行效率高 空间大,运行效率相对较低
存放规则 按顺序存放,先进后出 无序存储,可根据引用(地址)直接获取

2. 什么是变量提升(Hoisting)

var 或者函数声明会在执行上下文中会将当前变量声明提升到当前作用域的最前面,函数表达式不会提升

  1. 函数声明与变量相比,会被优先提升,所以如果出现重名,会以函数声明为主
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 先访问后赋值
console.log(num); // undefined
var num = 64;

// 先赋值,后访问,再声明
num = 64;
console.log(num); // 64
var num;

// 重名,函数声明会被优先提升
var num;
console.log(num); // ƒ num() {console.log('fun')}
var num = 64;
function num() {console.log('fun')};

var num;
console.log(num); // ƒ num() {console.log('fun')}
function num() {console.log('fun')};
var num = 64;

3.typeof NaN typeof null 返回什么?为什么?

  1. typeof(NaN) 返回 number. 因为 NaN 和 Number.NaN 的值相同。
  2. typeof(null) 返回 object. null 表示空对象的引用并且在二进制存储中正好全是 000

4. 类型转换

  1. == 和 === 的区别
    双等号会将左边的变量默认作类型转换再来进行判断, === 既判断值也判断类型,不会做类型转换

  2. console.log([] == false)打印什么?
    true, 空数组在双等号下面会作类型转换, 空数组转换空字符串然后转换为0 最后转换为布尔值 为 false

  3. 什么情况下会发生隐式类型转换

    • 字符串相加
    • 运算符计算 + - * / > < 等
    • ==
    • 逻辑非运算

转换规则

  1. 加号比较特殊,因为 + 既可以表示字符串拼接,也表示加法,所以加号需要单独对待,在求值时,首先将两个操作数强制转换为基本类型,然后检查两个操作数的类型:
  • 如果有一方是字符串,另一方则会被转换为字符串,并且它们连接起来。
  • 如果双方都是 BigInt,则执行 BigInt 加法。如果一方是 BigInt 而另一方不是,会抛出 TypeError。
  • 否则,双方都会被转换为数字,执行数字加法。

    强制类型转换

    因为 js 是一个弱类型语言,这意味着你经常可以使用与预期类型不同类型的值,并且该语言将为你转换它为正确的类型。为此,JavaScript 定义了少数强制规则:

    强制原始值转换: @@toPrimitive → valueOf() → toString()
    强制数字类型,number 类型,bigint类型转换:@@toPrimitive → valueOf() → toString()
    强制字符串类型转换: @@toPrimitive → toString() → valueOf()

    1
    console.log({} + []) // "[object object]"

    {} [] 都没有 toPrimitive 方法, object.prototype.valueOf() 返回对象自身,{} toString() 返回 “[object object]”, [] toString() 返回 “”, 所以打印结果是 “[object object]”

    相关练习题:

    1
    2
    3
    4
    5
    6
    1 + '2' // '12' 与字符串相加将非字符串类型转换为字符串类型相加
    1 + false // 1
    1 + [] // '1'
    1 + {} // '1[object object]'
    1 + + 'foo' // NaN
    '1' + + 'foo' // '1NaN'
  1. 减乘除
    我们在对各种非 Number 类型运用数学运算符(- * /)时,会先将非 Number 类型转换为 Number 类型。

    1
    2
    3
    4
    5
    6
    7
    8
    1 - true  // 0
    1 - null // 1
    1 * undefined // NaN undefined 转换为数字是 NaN
    2 * [5] // [5] 转换为数字是 '5'
    2 * [5,6,7] // NaN [5,6,7] 转换成数字是 NaN
    1 - '2' // -1 // 字符串和 number 做减法时,字符串先转换为 number
    '2' - 1 // 1
    [] - 2 // -2
  1. 逻辑语句中的类型转换
    在 if while for 中会遇到期望表达式返回一个 boolean。所以也会发生类型转换。

    1. 单个变量

    变量首先会考虑根据原始类型转换规则转换为布尔值,这里有以下规则:

    null undefined ‘’ false NaN 0 都表示 false ,但是其他情况包括 [] {} 表示true。

    1. ==

    遵循以下规则:

    1. NaN 与其他任何类型相比较都是 false, 包括它自己
    2. Boolean 和其他任何类型比较,Boolean 首先被转换为 Number 类型。
    3. String 和 Number 比较,先将 String 转换为 Number 类型。
    4. null == undefined 比较结果是 true,除此之外,null、undefined 和其他任何结果的比较值都为 false。
    5. 原始类型和引用类型做比较时,引用类型会依照 ToPrimitive 规则转换为原始类型
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    [] == ![] // true
    [undefined] == false // true

    var a = [1,2,3]
    var b = [1,2,3]
    var c = '1,2,3';

    a == c // true 数组和字符串比较,数组会转换成逗号拼接的字符串
    b == c // true
    a == b // false 变量表示的是数组这种引用类型的地址,而不是值,两个地址不一样所以不相等

5. const 定义的值一定不能改变吗?为什么?

const 可以定义基础数据类型或者引用类型,定义的基础数据类型则不能修改,因为当前变量赋值的就是当前栈内存地址中保存的值,但是当定义的是引用数据时,当前变量保存的是引用地址的指针,所以当前指针是固定的,但是值可以修改,比如对象或者数组。

6. 值引用 & 引用复制

  • 简单类型: undefined null number string boolean symbol bigint 都是通过值复制来赋值 / 传递
  • 复合类型: 数组,对象, 函数 往往通过引用复制来赋值/传递: 直接修改引用是不能改变原值的
  • 解构赋值避免引用复制

    1
    2
    3
    let obj = { name: 'obj' }
    let { name } = obj; // 解构赋值,是声明了一个新的变量 name 并赋初始值 obj
    name = 'other name';
  • 深拷贝 & 浅拷贝, 浅拷贝的是变量的引用,共享内存地址

    1
    2
    3
    4
    5
    6
    7
    8
    // 浅拷贝
    let a = [1,2,3];
    let b = [4,5,6];
    let c = Object.assign(a, b); // [4,5,6]
    c[0] = 1; // c = [1,5,6] a = [1,5,6]
    // 深拷贝
    let c = Object.assign([], a); // Object.assign({}, a) 同理
    c[0] = 4; // c = [4,2,3] a = [1,2,3]

7. valueOf() & toString()

  • valueOf 返回对象自身,toString 表示当前数据类型 string 之后的值。这两个属性会在类型转换时自动调用,调用顺序参考上方强制类型转换规则
  • number.toString() 会报错,1..toString() 不会报错,涉及到js引擎解析小数点的逻辑,通常number 后面的第一个点会当成小数点解析,第二个则会当成访问对象属性的点。
  • 这两个方法都可以被重写
1
2
3
4
5
6
7
8
// 实现 a == 1 && a== 2 && a == 3 
var a = {
value: 0,
toString() {
this.value++;
return this.value;
}
}

8. null 与 undefined 的区别

  • 相同点:
    • 都是基本类型
    • 都存储在栈中
    • null == false undefined == false 都为true
  • 不同点:
    • Number(null) === 0
    • Number(undefined) 是 NaN
  • 特点:
    • undefined == null